#ifndef _AMANITA_SET_FRACTAL_H
#define _AMANITA_SET_FRACTAL_H

/**
 * @file amanita/set/Fractal.h  
 * @author Per Löwgren
 * @date Modified: 2012-11-02
 * @date Created: 2004-03-07
 */ 

#include <amanita/Geometry.h>


/** Amanita Namespace */
namespace a {


enum {
	FRACT_FLOAT				= 0x0001,
	FRACT_EDGETRACE		= 0x0002,	//<! Use edge tracing (experimental)
	FRACT_THREADED			= 0x0004,	//<! Use threaded rendering
	FRACT_BUFFERED			= 0x0008,	//<! Use buffered rendering
	FRACT_DBUFFERED		= 0x0010,	//<! Use double buffers (not implemented)
	FRACT_BLOCKRENDER		= 0x0020,	//<! Render in blocks (not implemented)
};

enum {
	ITER_MIN					= 0x0064,	//<! Minimum iterations
	ITER_MAX					= 0x0400,	//<! Maximum iterations
	ITER_COLOR				= 0x03FF,	//<! Colour bits for iterations
	ITER_ALT					= 0x07FF,	//<! Alternative colour bits generated by decomposition
	ITER_BG					= 0x0800,	//<! Background flag
	ITER_EDGE				= 0x1000,	//<! Edge flag, set when using edge tracing
	ITER_FILL				= 0x2000,	//<! Fill flag, set when using edge tracing
};

enum {
	SET_JULIA,					//<! The Julia set
	SET_MANDELBROT,			//<! The Mandelbrot set
	SET_NUM
};

enum {
	PL_MU,
	PL_INVMU,
	PL_INVMU25,
	PL_LAMBDA,
	PL_INVLAMBDA,
	PL_INVLAMBDA1,
	PL_INVMU14,
	PL_INVMU2,
	PL_LAMBDANEG,
	PL_LAMBDASWAP,
	PL_NUM,
};

/** Escape time algorithms
 * Where Zr is the real part of Z, and Zi the imaginary, B is bailout value (escape radius) */
enum {
	ET_CIRCLE,					//<! Zr*Zr+Zi*Zi  > B*B
	ET_RECTANGLE,				//<! Zr           > B  or   Zi       > B
	ET_AND,						//<! Zr*Zr        > B  and  Zi*Zi    > B
	ET_PICKOVER,				//<! Zr*Zr        > B  or   Zi*Zi    > B
	ET_BIO,						//<! Abs(Zr)      > B  or   Abs(Zi)  > B
	ET_BIO2RI,					//<! Abs(2*Zr*Zi) > B
	ET_REAL,						//<! Zr           > B
	ET_ABSREAL,					//<! Abs(Zr)      > B
	ET_IMAGINARY,				//<! Zi           > B
	ET_MANHATTAN,				//<! ( (Abs(Zr) + Abs(Zi)) * (Abs(Zr) + Abs(Zi)) ) > B
	ET_MANR,						//<! (Zr+Zi) * (Zr+Zi) > B
	ET_NUM
};

/** Fractional Escape time algorithms */
enum {
	FE_PLAIN,					//<! Plain, no fractional escape time
	FE_LOGN,						//<! 
	FE_ZRINB,					//<! 
	FE_ABSZRB,					//<! 
	FE_ABSZRB2,					//<! 
	FE_ABSZIB,					//<! 
	FE_ABSZIB2,					//<! 
	FE_ABSZRIB,					//<! 
	FE_NUM						//<! 
};

/** Decomposition of exterior
 * Where N is number of iterations, Zr is the real part of Z, and Zi the imaginary, B is bailout value (escape radius) */
enum {
	DE_PLAIN,					//<! No decomposition
	DE_STRIPES,					//<! Modulo(N,1) == 1
	DE_BINARY,					//<! Zi      > 0
	DE_FEATHERS,				//<! Abs(Zr) < B   or   Abs(Zi) < B
	DE_1,							//<! Zr      > 0
	DE_2,							//<! Abs(Zr) < B
	DE_3,							//<! Abs(Zi) < B
	DE_4,							//<! Zr      > 0   and  Zi      > 0
	DE_5,							//<! Zr      > 0   or   Zi      > 0
	DE_6,							//<! Abs(Zr) < B   and  Abs(Zi) < B
	DE_NUM
};


/** @cond */
class Fractal;
class Thread;
/** @endcond */


struct ZoomNode {
	double x;
	double y;
	double z;
	int pause;
	ZoomNode *next;

	ZoomNode(double x,double y,double z) : x(x),y(y),z(z),pause(0),next(0) {}
	~ZoomNode() { if(next) delete next;next = 0; }
};


/** Zoom coordinates
 * @ingroup amanita */
struct Zoom {
	double x;				//<! X Coordinate of the zoom, the center of the zoom rectangle
	double y;				//<! Y Coordinate of the zoom, the center of the zoom rectangle
	double z;				//<! Zoom scale
	double width;			//<! Width of zoom rectangle
	double height;			//<! Height of zoom rectangle
	double left;			//<! Left point of zoom rectangle
	double top;				//<! Top point of zoom rectangle

	int index;
	int pause;
	int state;
	int steps;
	double sp;
	double zsp;

	Zoom() { x = y = width = height = left = top = 0.0,index = pause = state = steps = 0,sp = zsp = 0.0; }
};

/** The formula for the Mandelbrot and Julia sets, Z^2+C, where Z and C are complex numbers.
 * @ingroup amanita */
struct Formula {
	Fractal *fractal;		//<! Pointer to fractal
	Zoom *zoom;				//<! Pointer to zoom
	double pw;				//<! Point width
	double ph;				//<! Point height
	double ch;				//<! Hypotenuse of cr-0.25 and ci;
	double z;				//<! Zoom scale

	double zr;				//<! Real part of Z
	double zi;				//<! Imaginary part of Z
	double zrn;				//<! Real part of Z^n - to minimize number of multiplications
	double zin;				//<! Imaginary part of Z^n - to minimize number of multiplications
	double cr;				//<! Real part of C
	double ci;				//<! Imaginary part of C
	double b;				//<! Bailout, Escape Radius
	double b2;				//<! Bailout ^2, Escape Radius ^2

	int i;					//<! Iterations, number of iterations of Z^2+C
	int min;					//<! Minimum number of iterations
	int max;					//<! Maximum number of iterations
	long tot;				//<! Total number of iterations

	/** Constructor*/
	Formula() { pw = ph = ch = z = zr = zi = zrn = zin = cr = ci = b = b2 = 0.0,i = min = max = 0,tot = 0; }
	/** Iterate until reaching max or 0.
	 * @param e Escape time */
	void iterate(int e);
	/** Periodic iteration, testing for periodic cycles
	 * @param e Escape time */
	void periodic(int e);
	/** Recalculate iterations i to a colour index
	 * @param f Fractional escape algorithm
	 * @param d Decomposition algorithm
	 * @return A colour index value ranging between 0-ITER_COLOR, that may have flags set from the ITER_* enum */
	uint16_t colorIndex(int f,int d);
	/** Recalculate iterations i to a float value
	 * @param f Fractional escape algorithm
	 * @param d Decomposition algorithm
	 * @return A float value ranging between (-ITER_COLOR)-(+ITER_COLOR), where a value 0f 0.0 is inside the set. */
	double colorFloat(int f,int d);
	/** Iterate through the Julia set
	 * @param x X point in the set
	 * @param y Y point in the set */
	void julia(double x,double y);
	/** Iterate through the Mandelbrot set
	 * @param x X point in the set
	 * @param y Y point in the set */
	void mandelbrot(double x,double y);
};

/** A function pointer to a function of the Formula class
 * @see void Formula::julia(double,double)
 * @see void Formula::mandelbrot(double,double) */
typedef void (Formula::*formula_func)(double,double);


/** A class for calculating fractals of the Mandelbrot and Julia sets.
 * 
 * See examples/fractal.cpp on how to use this class.
 * 
 * @ingroup amanita */
class Fractal {
/** @cond */
friend class Formula;
/** @endcond */
protected:
	uint32_t params;			//<! Parameters
	int sw;						//<! Width in pixels
	int sh;						//<! Height in pixels
	double x_point;			//<! X of point to zoom
	double y_point;			//<! Y of point to zoom
	double width;				//<! Width as double
	double height;				//<! Height as double
	ZoomNode *first;			//<! 
	ZoomNode *last;			//<! 
	ZoomNode *node;
	Zoom zoom;					//<! Zoom coordinates in fractal
	rect16_t zrect;			//<! Zoom coordinates in pixels
	Formula formula;			//<! The formula for calculating the fractal.
	int set;						//<! Fractal set, Mandelbrot or Julia
	int plane;					//<! 
	int escape;					//<! Escape time algorithm
	int fraction;				//<! Fractional escape time algorithm
	int decomp;					//<! Decomposition algorithm
	int range;					//<! Iteration range, that is maximum iterations is the sum of minimum of previous calculation plus range
	float radius;				//<! Escape radius/Bailout
	uint16_t *index1;			//<! Colour back buffer
	uint16_t *index2;			//<! Colour buffer
	float *float1;				//<! Colour back buffer
	uint8_t *map;				//<! 

	double percent;			//<! Percent of how much of the fractal has been calculated, range from 0-100, or if reset -1
	long time;					//<! Time in microseconds to calculate fractal
	int nthreads;				//<! Number of threads
	Thread *threads;			//<! Threads used if FRACT_THREADED is set
	uint32_t thfin;			//<! Threads finish bitmap, when each thread finish it's bit is set, and when all bits are set the finish routine is called

	/** Called when calculations has finished
	 * @see void calculate(Formula &,int,int,int,int,int,formula_func) */
	void finish(int t);

public:
	/** Constructor
	 * @param w Width in pixels
	 * @param h Height in pixels
	 * @param p Parameters, can be any of the FRACT_* enum
	 * @param f Fractal to copy settings and style from */
	Fractal(int w,int h,int p=0,Fractal *f=0);
	/** Destructor */
	~Fractal();

	void setSize(int w,int h);

	double getX() { return zoom.x; }
	double getY() { return zoom.y; }
	int getWidth() { return sw; }
	int getHeight() { return sh; }

	uint8_t *getMap() { return map; }

	/** Render fractal
	 * @param i Maximum number of iterations, if zero an arbitrary value is selected based on results in previous calculation */
	void render(int i=0);
	/** Calculate a portion of the fractal, use render instead
	 * @param form Formula object containing settings for this fractal
	 * @param n Index of thread calling this method
	 * @param l Left edge of area to be calculated
	 * @param t Top edge of area to be calculated
	 * @param r Right edge of area to be calculated
	 * @param b Bottom edge of area to be calculated
	 * @param f Function pointer to be used
	 * @see void render(int)
	 * @see uint16_t (Formula::*formula_func)(double,double) */
	void calculate(Formula &form,int n,int l,int t,int r,int b,formula_func f);
	/** Flip back buffer to front */
	void flip();

	void restart(int s=-1);
	/** Randomly generate settings for a new fractal, and reset zoom
	 * @param s Set to use (Mandelbrot or Julia), or if -1 randomly select */
	void random(int s=-1);
	/** Randomly select a new style without altering the zoom or Set */
	void randomStyle();
	/** Increase range of max iterations, negative values will decrease the range. To set a specific maximum iterations, set s in range method
	 * @param i Number of maximum iterations to increase */
	void increaseRange(int i);
	/** Set escape radius/bailout
	 * @param r Escape radius value */
	void setRadius(float r) { radius = r;}
	/** Increase escape radius/bailout
	 * @param i If true, increase relative to present radius, else decrease */
	void increaseRadius(bool i);
	/** Zoom in at zoom.x, zoom.y */
	void zoomIn();
	/** Zoom in at x, y, using z to increase or decrease scale
	 * @param x X coordinate to zoom in
	 * @param y Y coordinate to zoom in
	 * @param z Zoom rate, if 1.0 zoom remain the same, if >0.0 and <1.0 zooming in, and >1.0 zooming out */
	void zoomIn(int x,int y,double z=0.0);
	/** Zoom out at zoom.x, xoom.y */
	void zoomOut();

	void setSet(int s);
	void setPlane(int p);
	/** Set fractal style
	 * @param s A string containing a hexadecimal value generated by print functions
	 * @see int print(char *)
	 * @see void print(FILE *) */
	void setStyle(const char *s);
	/** Set fractal style
	 * @param s Set, either of the SET_* enum
	 * @param e Escape time algorithm,  either of ET_*
	 * @param f Fractional escape time algorithm, either of FE_*
	 * @param d Decomposition algorithm, either of DE_* */
	void setStyle(int s,int e,int f,int d);

	/** Reset calculation of fractal (percent set to zero) */
	void reset() { percent = 0.0; }
	/** Get percent of calculation
	 * @return Integer of percent, 0-100 */
	int getPercent() { return (int)percent; }
	/** Get colour index buffer
	 * @return Array of unsigned 16bit integer */
	uint16_t *getIndex() { return index2; }
	/** Get colour index buffer
	 * @return Array of unsigned 16bit integer */
	float *getFloat() { return float1; }
	/** Set zoom
	 * @param x X coordinate in fractal
	 * @param y Y coordinate in fractal
	 * @param z Zoom scale, where 1.0 is 1/1
	 * @param r Zoom rate */
	void setZoom(double x,double y,double z=1.0,double r=1.0);
	double getZoom() { return zoom.z; }
	/** Get zoom rect relative to screen coordinates in pixels
	 * @param x X point on screen to zoom
	 * @param y Y point on screen to zoom
	 * @param z Zoom rectangle containing the left, top, width and height */
	void getZoom(int &x,int &y,rect16_t &z) { x = (int)x_point,y = (int)y_point,z = zrect; }

	void resetZoom();
	void addZoomNode();
	void nextZoomNode();
	void startZoom();
	bool nextZoom();
	bool renderZoom(int i=0);
	bool isZooming() { return zoom.steps>0 && zoom.index<=zoom.steps; }
	int getZoomFrame() { return zoom.index; }
	int getZoomPercent() { return zoom.steps>0? zoom.index*100/zoom.steps : 0; }

	/** Enable edge tracing when calculating fractal */
	void enableEdgeTracing() { params |= FRACT_EDGETRACE; }
	/** Disable edge tracing when calculating fractal */
	void disableEdgeTracing() { params &= ~FRACT_EDGETRACE; }
	/** Whether using edge tracing
	 * @return True if edge tracing is used */
	bool usingEdgeTracing() { return (params&FRACT_EDGETRACE); }

	void write(FILE *fp);
	void read(FILE *fp);
};


}; /* namespace a */



#endif /* _AMANITA_SET_FRACTAL_H */

